package com.yuma.tingshu.live.api;

import com.alibaba.fastjson.JSON;
import com.yuma.tingshu.common.constant.RedisConstant;
import com.yuma.tingshu.common.util.LiveRoomContainer;
import com.yuma.tingshu.model.live.FromUser;
import com.yuma.tingshu.model.live.SocketMsg;
import com.yuma.tingshu.vo.user.UserInfoVo;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * WebSocket常用注解：
 *
 * @ServerEndpoint 通过这个 spring boot 就可以知道你暴露出去的 ws 应用的路径，
 * 有点类似我们经常用的@RequestMapping。比如你的启动端口是8080，
 * 而这个注解的值是api，那我们就可以通过 ws://127.0.0.1:8080/api 来连接你的应用
 * @OnOpen 当websocket建立连接成功后会触发这个注解修饰的方法，注意它有一个Session参数
 * @OnClose 当websocket建立的连接断开后会触发这个注解修饰的方法，注意它有一个Session参数
 * @OnMessage 当客户端发送消息到服务端时，会触发这个注解修改的方法，它有一个String入参表明客户端传入的值
 * @OnError 当websocket建立连接时出现异常会触发这个注解修饰的方法，注意它有一个Session参数
 */
@Controller
@ServerEndpoint("/api/websocket/{roomId}/{token}")
public class WebSocketApiController {
    // Cannot invoke "org.springframework.data.redis.core.RedisTemplate.opsForValue()" because "this.redisTemplate" is null
    // WebSocket 服务器端点的管理 和SpringBean的管理机制不完全相同，导致出现了空指针异常

    /**
     * RedisTemplate 本身是线程安全的（Spring 对其进行了线程安全设计），可以被多线程 / 多实例共享使用；
     * 避免每个 WebSocket 实例都创建一个 RedisTemplate 对象，节省资源；
     * 确保所有连接都能通过同一个 RedisTemplate 操作 Redis，保持数据一致性
     */
    private static RedisTemplate redisTemplate;

    /**
     * Spring 的依赖注入（DI）机制主要针对实例变量（非静态字段），默认不会对 static 修饰的变量进行注入。
     * 如果直接写成：
     * @Autowired
     * private static RedisTemplate redisTemplate; // 错误写法，Spring 不会注入
     * Spring 会忽略这个静态字段，最终 redisTemplate 会保持 null，导致后续使用时抛出空指针异常。
     */
    @Autowired
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        WebSocketApiController.redisTemplate = redisTemplate;
    }

    @OnOpen
    public void open(Session session, @PathParam("roomId") Long roomId, @PathParam("token") String token) {
        System.out.println("服务器端：直播间" + roomId + "建立了连接");
        //回显一句话：xxx进入直播间
        //1.获取当前用户的昵称
        //UserInfoVo userInfoVo = new UserInfoVo();
        UserInfoVo userInfoVo = (UserInfoVo) this.redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
        //2.创建SocketMsg对象
        SocketMsg socketMsg = new SocketMsg();
        socketMsg.setMsgContent(userInfoVo.getNickname() + "123进入了直播间");
        socketMsg.setMsgType(SocketMsg.MsgTypeEnum.JOIN_CHAT.getCode());
        socketMsg.setTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        socketMsg.setLiveRoomId(roomId);
        FromUser fromUser = new FromUser();
        fromUser.setAvatarUrl(userInfoVo.getAvatarUrl());
        fromUser.setUserId(userInfoVo.getId());
        fromUser.setNickname(userInfoVo.getNickname());
        socketMsg.setFromUser(fromUser);
        //String jsonString = JSON.toJSONString(socketMsg);
        LiveRoomContainer.join(roomId, userInfoVo.getId(), session);
        //3.返回消息
        //session.getAsyncRemote().sendText(jsonString);
        //LiveRoomContainer.sendMessage(socketMsg);
        //rabbitmq中convertAndSend，发送消息给交换机，和发布订阅无关(或者你认为rabbitqm本身就是发布订阅)
        //redisTemplate的convertAndSend就说明使用的肯定是发布订阅模式
        //publisher在发送消息
        //发布 "用户进入" 消息到 Redis，让所有服务器节点都能收到并转发给各自连接的客户端
        redisTemplate.convertAndSend(RedisConstant.LIVE_MESSAGE_CHANNEL, socketMsg);
    }

    /*
    {"fromUser":
        {"avatarUrl":"https://pic.rmb.bdstatic.com/bjh/news/0abff0af846626ee3139e852598d3045.png",
        "nickname":"听友10021",
        "userId":29},
      "liveRoomId":"4",
      "msgContent":"1",
      "msgType":"1"}
     */
    @OnMessage
    public void doMessage(Session session, String message, @PathParam("roomId") Long roomId, @PathParam("token") String token) {
        System.out.println("服务器端：" + message);
        //消息返回（1.没有做任何处理，2.返回给发送者自己 3.心跳消息也返回）
        //session.getAsyncRemote().sendText(message);
        //消息返回：1.心跳消息不返回  2.添加时间  3.消息的类型使用枚举表示
        SocketMsg socketMsg = JSON.parseObject(message, SocketMsg.class);
        if (!SocketMsg.MsgTypeEnum.HEART_BEAT.getCode().equals(socketMsg.getMsgType())) {
            socketMsg.setTime(new Date().toLocaleString()); //还可以通过SimpleDateFormat来定义格式
            socketMsg.setMsgType(SocketMsg.MsgTypeEnum.PUBLIC_MSG.getCode());
            //String jsonString = JSON.toJSONString(socketMsg);
            //session.getAsyncRemote().sendText(jsonString);
            //LiveRoomContainer.sendMessage(socketMsg);
            redisTemplate.convertAndSend(RedisConstant.LIVE_MESSAGE_CHANNEL, socketMsg);
        }
    }

    @OnError
    public void doError(Throwable throwable) {
        System.out.println("服务器端：" + throwable.getMessage());

    }

    @OnClose
    public void close(Session session, @PathParam("roomId") Long roomId, @PathParam("token") String token) {
        System.out.println("服务器端：直播间" + roomId + "断开了连接");
        //回显一句话：xxx进入直播间
        //1.获取当前用户的昵称
        //UserInfoVo userInfoVo = new UserInfoVo();
        UserInfoVo userInfoVo = (UserInfoVo) this.redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
        //2.创建SocketMsg对象
        SocketMsg socketMsg = new SocketMsg();
        socketMsg.setMsgContent(userInfoVo.getNickname() + "退出了直播间");
        socketMsg.setMsgType(SocketMsg.MsgTypeEnum.CLOSE_SOCKET.getCode());
        socketMsg.setTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        socketMsg.setLiveRoomId(roomId);
        FromUser fromUser = new FromUser();
        fromUser.setAvatarUrl(userInfoVo.getAvatarUrl());
        fromUser.setUserId(userInfoVo.getId());
        fromUser.setNickname(userInfoVo.getNickname());
        socketMsg.setFromUser(fromUser);
        //String jsonString = JSON.toJSONString(socketMsg);
        LiveRoomContainer.leave(roomId, userInfoVo.getId(), session);
        //3.返回消息 !!!!  都已经退出了
        //Message will not be sent because the WebSocket session has been closed
        //解决方案：多人聊天下由其他一个没有关闭的session来发送这个消息给其他没有退出群聊的用户
        //session.getAsyncRemote().sendText(jsonString);
        //LiveRoomContainer.sendMessage(socketMsg);
        redisTemplate.convertAndSend(RedisConstant.LIVE_MESSAGE_CHANNEL, socketMsg);

    }
}
